#include "config.h"
#include "mips-formats.h"
#include "mips-instr.h"
#include "mips-regdef.h"
#include "opcode/mips.h"
#include <ctype.h>
#include <stdio.h>
#include <string>
#include <vector>
static void replace_opname(char *name) {
  while (*name) {
    if (*name == '.')
      *name = '_';
    name++;
  }
}

static std::vector<std::string> instr;

#define define_opcode(t, s, opname, fmt, fmt2, ...)                            \
  do {                                                                         \
    char name[20];                                                             \
    char def[50];                                                              \
    strcpy(name, opname);                                                      \
    replace_opname(name);                                                      \
    if (strlen(fmt))                                                           \
      snprintf(def, 50, "#define mips32_%s(p,%s)", name, fmt);                 \
    else                                                                       \
      snprintf(def, 50, "#define mips32_%s(p)", name);                         \
    instr.push_back(def);                                                      \
    snprintf(newop, 256, "%-40s mips32_%s_op(p," fmt2 ")", def, t,             \
             __VA_ARGS__);                                                     \
  } while (0)

#define define_opcode_r4(s, opname, fmt, fmt2, ...)                            \
  define_opcode("r4", s, opname, fmt, fmt2, __VA_ARGS__)
#define define_opcode_f(s, opname, fmt, fmt2, ...)                             \
  define_opcode("f", s, opname, fmt, fmt2, __VA_ARGS__)
#define define_opcode_r(s, opname, fmt, fmt2, ...)                             \
  define_opcode("r", s, opname, fmt, fmt2, __VA_ARGS__)
#define define_opcode_i(s, opname, fmt, fmt2, ...)                             \
  define_opcode("i", s, opname, fmt, fmt2, __VA_ARGS__)

#define define_opcode_j(s, opname, fmt, fmt2, ...)                             \
  define_opcode("j", s, opname, fmt, fmt2, __VA_ARGS__)

void dump_opcode_r4_type() {
  char **code = mips_instrs.code_r4_type;
  for (int i = 0; i < bfd_mips_num_builtin_opcodes; i++) {
    struct mips_opcode *op = &mips_opcodes[i];

    if (opcode_is_member(op, ISA_MIPS32R2, 0, CPU_R3000)) {
      if ((op->pinfo & INSN_MACRO) == INSN_MACRO)
        continue;
      if ((op->pinfo & (INSN_UDI | INSN_TLB)) == 0) {
        if (op->ase == 0) {
          for (int j = 0; j < mips_instrs.r4_code_size; j++) {
            if (strcmp(code[j], op->name) == 0) {
              int special = (op->match & op->mask) >> 26;
              int func = ((op->mask & op->match) >> 21) & 0x1f;
              int rt = (op->mask >> 16) & 0x1f ? (op->match >> 16) & 0x1f : -1;
              int rd = (op->mask >> 11) & 0x1f ? (op->match >> 11) & 0x1f : -1;
              int imm = ((op->match & op->mask) >> 6) & 0x1f;
              int opcode = (op->match & op->mask) & 0x3f;
              char newop[256] = {0};
              if (((op->mask >> 6) & 0x1f) == 0 && rt == -1 && rd == -1) {
                define_opcode_r4(newop, op->name, "rt,rd,imm",
                                 "0x%02x,imm,0x%02x,0x%02x,rt,rd", opcode, func,
                                 special);
                printf("%s\n", newop);

                /* printf("\t%s %s 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
                 */
                /*        op->name,op->args, */
                /*        op->match,op->mask,op->pinfo,op->pinfo2, */
                /*        op->membership,op->ase,op->exclusions); */
              }
            }
          }
        }
      }
    }
  }
}

void dump_opcode_r_type() {
  char **code = mips_instrs.code_r_type;
  for (int i = 0; i < bfd_mips_num_builtin_opcodes; i++) {
    struct mips_opcode *op = &mips_opcodes[i];

    if (opcode_is_member(op, ISA_MIPS32R2, 0, CPU_R3000)) {
      if ((op->pinfo & INSN_MACRO) == INSN_MACRO)
        continue;
      if ((op->pinfo & (INSN_UDI | INSN_TLB)) == 0) {
        if (op->ase == 0) {
          for (int j = 0; j < mips_instrs.r_code_size; j++) {
            if (strcmp(code[j], op->name) == 0) {

              int special = (op->match & op->mask) >> 26;
              int rs = (op->mask >> 21) & 0x1f ? (op->match >> 21) & 0x1f : -1;
              int rt = (op->mask >> 16) & 0x1f ? (op->match >> 16) & 0x1f : -1;
              int rd = (op->mask >> 11) & 0x1f ? (op->match >> 11) & 0x1f : -1;
              int fmt = ((op->match & op->mask) >> 6) & 0x1f;
              int opcode = (op->match & op->mask) & 0x3f;
              char newop[256] = {0};
              if (((op->mask >> 26) & 0x3f) == 0)
                continue;

              if (((op->mask >> 0) & 0x3f) != 0x3f)
                continue;

              if (rs == -1 && rt == -1 && rd == -1) {
                define_opcode_r(newop, op->name, "rs,rt,rd",
                                "0x%02x,0x%02x,0x%02x,rs,rt,rd", opcode, fmt,
                                special);
              } else if (rs != -1 && rt == -1 && rd == -1) {
                define_opcode_r(newop, op->name, "rt,rd",
                                "0x%02x,0x%02x,0x%02x,0x%02x,rt,rd", opcode,
                                fmt, special, rs);
              } else if (rs == -1 && rt != -1 && rd == -1) {
                define_opcode_r(newop, op->name, "rs,rd",
                                "0x%02x,0x%02x,0x%02x,rs,0x%02x,rd", opcode,
                                fmt, special, rt);
              } else if (rs == -1 && rt == -1 && rd != -1) {
                define_opcode_r(newop, op->name, "rs,rt",
                                "0x%02x,0x%02x,0x%02x,rs,rt,0x%02x", opcode,
                                fmt, special, rd);
              } else if (rs != -1 && rt != -1 && rd == -1) {
                define_opcode_r(newop, op->name, "rd",
                                "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,rd", opcode,
                                fmt, special, rs, rt);
              } else if (rs != -1 && rt == -1 && rd != -1) {
                define_opcode_r(newop, op->name, "rt",
                                "0x%02x,0x%02x,0x%02x,0x%02x,rt,0x%02x", opcode,
                                fmt, special, rs, rd);
              } else if (rs == -1 && rt != -1 && rd != -1) {
                define_opcode_r(newop, op->name, "rs",
                                "0x%02x,0x%02x,0x%02x,rs,0x%02x,0x%02x", opcode,
                                fmt, special, rt, rd);
              } else if (rs != -1 && rt != -1 && rd != -1) {
                define_opcode_r(newop, op->name, "",
                                "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x",
                                opcode, fmt, special, rs, rt, rd);
              }
              printf("%s\n", newop);
              /* printf("\t%s %s 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
               */
              /*        op->name,op->args, */
              /*        op->match,op->mask,op->pinfo,op->pinfo2, */
              /*        op->membership,op->ase,op->exclusions); */
            }
          }
        }
      }
    }
  }
}

void dump_opcode_f_type() {
  char **code = mips_instrs.code_f_type;
  for (int i = 0; i < bfd_mips_num_builtin_opcodes; i++) {
    struct mips_opcode *op = &mips_opcodes[i];

    if (opcode_is_member(op, ISA_MIPS32R2, 0, CPU_R3000)) {
      if ((op->pinfo & INSN_MACRO) == INSN_MACRO)
        continue;
      if ((op->pinfo & (INSN_UDI | INSN_TLB | INSN_COP)) == 0) {
        if (op->ase == 0) {
          for (int j = 0; j < mips_instrs.f_code_size; j++) {
            if (strcmp(code[j], op->name) == 0) {
              int special = (op->match & op->mask) >> 26;
              int fs = (op->mask >> 11) & 0x1f ? (op->match >> 11) & 0x1f : -1;
              int ft = (op->mask >> 16) & 0x1f ? (op->match >> 16) & 0x1f : -1;
              int fd = (op->mask >> 6) & 0x1f ? (op->match >> 6) & 0x1f : -1;
              int fmt = ((op->match & op->mask) >> 21) & 0x1f;
              int opcode = (op->match & op->mask) & 0x3f;
              char newop[256] = {0};
              if (fs == -1 && ft == -1 && fd == -1) {
                define_opcode_f(newop, op->name, "fs,ft,fd",
                                "0x%02x,0x%02x,0x%02x,fs,ft,fd", opcode, fmt,
                                special);
              } else if (fs != -1 && ft == -1 && fd == -1) {
                define_opcode_f(newop, op->name, "ft,fd",
                                "0x%02x,0x%02x,0x%02x,0x%02x,ft,fd", opcode,
                                fmt, special, fs);
              } else if (fs == -1 && ft != -1 && fd == -1) {
                define_opcode_f(newop, op->name, "fs,fd",
                                "0x%02x,0x%02x,0x%02x,fs,0x%02x,fd", opcode,
                                fmt, special, ft);
              } else if (fs == -1 && ft == -1 && fd != -1) {
                define_opcode_f(newop, op->name, "fs,ft",
                                "0x%02x,0x%02x,0x%02x,fs,ft,0x%02x", opcode,
                                fmt, special, fd);
              } else if (fs != -1 && ft != -1 && fd == -1) {
                define_opcode_f(newop, op->name, "fd",
                                "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,fd", opcode,
                                fmt, special, fs, ft);
              } else if (fs != -1 && ft == -1 && fd != -1) {
                define_opcode_f(newop, op->name, "ft",
                                "0x%02x,0x%02x,0x%02x,0x%02x,ft,0x%02x", opcode,
                                fmt, special, fs, fd);
              } else if (fs == -1 && ft != -1 && fd != -1) {
                define_opcode_f(newop, op->name, "fs",
                                "0x%02x,0x%02x,0x%02x,fs,0x%02x,0x%02x", opcode,
                                fmt, special, ft, fd);
              } else if (fs != -1 && ft != -1 && fd != -1) {
                define_opcode_f(newop, op->name, "",
                                "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x",
                                opcode, fmt, special, fs, ft, fd);
              }
              printf("%s\n", newop);
              // printf("\t%s %s 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
              //        op->name,op->args,
              //        op->match,op->mask,op->pinfo,op->pinfo2,
              //        op->membership,op->ase,op->exclusions);
            }
          }
        }
      }
    }
  }
}

void dump_opcode_i_type() {
  char **code = mips_instrs.code_i_type;
  for (int i = 0; i < bfd_mips_num_builtin_opcodes; i++) {
    struct mips_opcode *op = &mips_opcodes[i];

    if (opcode_is_member(op, ISA_MIPS32R2, 0, CPU_R3000)) {
      if ((op->pinfo & INSN_MACRO) == INSN_MACRO)
        continue;

      if ((op->pinfo & (INSN_UDI | INSN_TLB | INSN_COP)) == 0) {
        if (op->ase == 0) {
          for (int j = 0; j < mips_instrs.i_code_size; j++) {
            if (strcmp(code[j], op->name) == 0) {
              int opcode = (op->match & op->mask) >> 26;
              int rs = (op->mask >> 21) & 0x1f ? (op->match >> 21) & 0x1f : -1;
              int rt = (op->mask >> 16) & 0x1f ? (op->match >> 16) & 0x1f : -1;
              char newop[256] = {0};
              if (rs == -1 && rt == -1) {
                define_opcode_i(newop, op->name, "rt,rs,imm",
                                "0x%02x,rs,rt,imm", opcode);
              } else if (rs == -1 && rt != -1) {
                define_opcode_i(newop, op->name, "rs,imm",
                                "0x%02x,rs,0x%02x,imm", opcode, rt);
              } else if (rs != -1 && rt == -1) {
                define_opcode_i(newop, op->name, "rt,imm",
                                "0x%02x,0x%02x,rt,imm", opcode, rs);
              } else if (rs != -1 && rt != -1) {
                define_opcode_i(newop, op->name, "imm",
                                "0x%02x,0x%02x,0x%02x,imm", opcode, rs, rt);
              }
              printf("%s\n", newop);
              /* printf("\t%s %s 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
               */
              /*        name,op->args, */
              /*        op->match,op->mask,op->pinfo,op->pinfo2, */
              /*        op->membership,op->ase,op->exclusions); */
            }
          }
        }
      }
    }
  }
}

void dump_opcode_j_type() {
  char **code = mips_instrs.code_j_type;
  for (int i = 0; i < bfd_mips_num_builtin_opcodes; i++) {
    struct mips_opcode *op = &mips_opcodes[i];

    if (opcode_is_member(op, ISA_MIPS32R2, 0, CPU_R3000)) {
      if ((op->pinfo & INSN_MACRO) == INSN_MACRO)
        continue;

      if ((op->pinfo & (INSN_UDI | INSN_TLB | INSN_COP)) == 0) {
        if (op->ase == 0) {
          for (int j = 0; j < mips_instrs.j_code_size; j++) {

            if (strcmp(code[j], op->name) == 0) {
              int opcode = (op->match & op->mask) >> 26;
              if ((op->mask & ((1 << 26) - 1)) == 0) {
                char newop[256] = {0};
                define_opcode_j(newop, op->name, "imm", "0x%02x,imm", opcode);
                printf("%s\n", newop);
                /* printf("\t%s %s 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx\n",
                 */
                /*        op->name,op->args, */
                /*        op->match,op->mask,op->pinfo,op->pinfo2, */
                /*        op->membership,op->ase,op->exclusions); */
              }
            }
          }
        }
      }
    }
  }
}

void gen_test_code() {
  printf("#include <stdio.h>\n");
  printf("int main(int argc,char* argv[])\n");
  printf("{\n");
  printf("int fd = 1;\n");
  printf("int fs = 2;\n");
  printf("int ft = 3;\n");
  printf("int rs = 4;\n");
  printf("int rd = 5;\n");
  printf("int rt = 6;\n");
  printf("uint32_t code[1000];\n");
  printf("uint8_t* p = (uint8_t*)code;\n");

  for (int i = 0; i < instr.size(); i++) {
    printf("%s;\n", instr[i].substr(8).c_str());
  }
  printf("for(int i = 0;i < %ld;i++) {\n", instr.size());
  printf("}\n");
  printf("}\n");
}
